package com.symaster.common.swing.listener;

import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.KeyEvent;
import java.util.*;

/**
 * <p>程序级全局快捷键</p>
 * <pre>{@code
 * // 使用以下方式注册全局快捷键
 * ApplicationShortcutManager.getInstance().addShortcutListener(new ShortcutManager.ShortcutListener() {
 *   public void handle() {
 *     System.out.println("Meta + I");
 *   }
 * }, KeyEvent.VK_META, KeyEvent.VK_I);
 *
 * ApplicationShortcutManager.getInstance().addShortcutListener(new ShortcutManager.ShortcutListener() {
 *   public void handle() {
 *     System.out.println("Ctrl + Meta + M");
 *   }
 * }, KeyEvent.VK_CONTROL, KeyEvent.VK_META, KeyEvent.VK_M);
 *
 * } </pre>
 *
 * @author MGZC
 * @time 2021/8/24 21:52
 */
public class ApplicationShortcutManager implements AWTEventListener {

    /**
     * 使用单态模式
     */
    private static final ApplicationShortcutManager instance = new ApplicationShortcutManager();

    /**
     * 快捷键与事件处理对象键值对
     */
    private final Map<String, ShortcutListener> listeners;

    /**
     * 某组件上发生了快捷键列表中的快捷键事件, 如果他的父组件在被忽略组件列表中, 则放弃这个事件.
     */
    private final Set<Component> ignoredComponents;

    /**
     * 保存键盘上键与它的ascii码对应
     * 如果以某键的ascii码为下标, 数组中此下标的值为true, 说明此键被按下了.
     * 当此键被释放开后, 数组中对应的值修改为false
     */
    private final boolean[] keys;

    private ApplicationShortcutManager() {
        keys = new boolean[1024];
        ignoredComponents = new HashSet<>();
        listeners = new HashMap<>();

        // 只关心键盘事件
        Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
    }

    public static ApplicationShortcutManager getInstance() {
        return instance;
    }

    /**
     * 有键盘事件发生
     */
    public void eventDispatched(AWTEvent event) {

        if (event.getClass() == KeyEvent.class) {
            KeyEvent ke = (KeyEvent) event;
            if (keys.length - 1 <= ke.getKeyCode()) {
                String format = String.format("[error] keys.len=(%s); keyCode=(%s)", keys.length, ke.getKeyCode());
                System.out.println(format);
                return;
            }

            if (ke.getID() == KeyEvent.KEY_PRESSED) {
                keys[ke.getKeyCode()] = true;
                // 查找快捷键对应的处理对象, 然后调用事件处理函数
                String shortcut = searchShortcut();
                ShortcutListener l = listeners.get(shortcut);
                if (l != null && !isIgnored(event)) {
                    l.handle();
                }
            } else if (ke.getID() == KeyEvent.KEY_RELEASED) {
                keys[ke.getKeyCode()] = false;
            }
        }
    }

    protected String searchShortcut() {

        // 每个键之间用一个"."来隔开.
        // 例如ctr + x的对应值为"17.88."
        StringBuilder shortcut = new StringBuilder();
        for (int i = 0; i < keys.length; ++i) {
            if (keys[i]) {
                shortcut.append(i).append(".");
            }
        }

        // System.out.println(shortcut.toString());
        return shortcut.toString();
    }

    /**
     * 查找此快捷键事件是否要被抛弃
     */
    protected boolean isIgnored(AWTEvent event) {
        if (!(event.getSource() instanceof Component)) {
            return false;
        }

        boolean ignored = false;
        for (Component com = (Component) event.getSource(); com != null; com = com.getParent()) {
            if (ignoredComponents.contains(com)) {
                ignored = true;
                break;
            }
        }

        return ignored;
    }

    public void removeShortcutListener(ShortcutListener l) {

        String tempKey = null;
        for (Map.Entry<String, ShortcutListener> e : listeners.entrySet()) {
            if (e.getValue().equals(l)) {
                tempKey = e.getKey();
            }
        }

        listeners.remove(tempKey);
    }

    public void addShortcutListener(ShortcutListener l, int... keys) {

        // 快捷键的对应值按它们的键顺序大小来进行创建
        StringBuilder sb = new StringBuilder();
        Arrays.sort(keys);

        for (int key : keys) {
            if (0 < key && key < this.keys.length) {
                sb.append(key).append(".");
            } else {
                System.out.println("Key is not valid");
                return;
            }
        }

        String shortcut = sb.toString();

        // 如果还不存在, 则加入
        if (listeners.containsKey(shortcut)) {
            System.out.println("The shourt cut is already used.");
        } else {
            listeners.put(shortcut, l);
        }
    }

    public void addIgnoredComponent(Component com) {
        ignoredComponents.add(com);
    }

    public void removeDiscardComponent(Component com) {
        ignoredComponents.remove(com);
    }

    public interface ShortcutListener {
        void handle();
    }
}

